package server;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoUtility {
	/*
	 * Calculate hash from a byte array which consists of: the challenge, uid, sequence number
	 */
	public static byte[] hash(String alg, String key, String challenge, String uid, String seq, String imei, String time){
		try{
			
			byte[] bUid = StringUtility.toByteArray(uid);
			byte[] bChallenge = StringUtility.toByteArray(challenge);
			byte[] bSequence = StringUtility.toByteArray(seq);
			byte[] bImei = StringUtility.toByteArray(imei);
			byte[] bTime = StringUtility.toByteArray(time);
			
			int szChallenge = bChallenge.length;
			int szUid = bUid.length;
			int szSequence = bSequence.length;
			int szImei = bImei.length;
			int szTime = bTime.length;
			
			//the message before hashing
			byte[] concatenation = new byte[szChallenge + szSequence + szUid + szImei + szTime];
	
			if (szChallenge > 0){
				System.arraycopy(bChallenge, 0, concatenation, 0, szChallenge);
			}
			
			System.arraycopy(bUid, 0, concatenation, szChallenge, szUid);
			System.arraycopy(bSequence, 0, concatenation, szChallenge + szUid, szSequence);
			System.arraycopy(bImei, 0, concatenation, szChallenge + szUid + szSequence, szImei);
			System.arraycopy(bTime, 0, concatenation, szChallenge + szUid + szSequence + szImei, szTime);
			
			//hash
			SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), alg);
			Mac mac = Mac.getInstance(alg);
			mac.init(keySpec);
		
			byte[] rawHash = mac.doFinal(concatenation);
			
			return rawHash;
		}
		catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	/*
	 * Rotate left by 8 bits
	 */
	public static byte[] rotateLeft(byte[] arr){
		byte[] rotatedArr = new byte[arr.length];
		System.arraycopy(arr, 1, rotatedArr, 0, 7);
		rotatedArr[7] = arr[0];
		return rotatedArr;
	}
	
	/*
	 * Rotate right by 8 bits
	 */
	public static byte[] rotateRight(byte[] arr){
		byte[] rotatedArr = new byte[arr.length];
		System.arraycopy(arr, 0, rotatedArr, 1, 7);
		rotatedArr[0] = arr[7];
		return rotatedArr;
	}
	/*
	 * generate a random number
	 * Input: the length of random number
	 */
	
	public static byte[] generateRandom(int len){
		SecureRandom sr = new SecureRandom();
		byte[] rndBytes = new byte[len];
		sr.nextBytes(rndBytes);
		return rndBytes;
	}
	
	/* 
	 * calculate session key in DESFire authentication
	 */
	public static byte[] sessionKey (byte[] RndA, byte[] RndB){
		byte[] sessionKey = new byte[16];
		System.arraycopy(RndA, 0, sessionKey, 0, 4);
		System.arraycopy(RndB, 0, sessionKey, 4, 4);
		System.arraycopy(RndA, 4, sessionKey, 8, 4);
		System.arraycopy(RndB, 4, sessionKey, 12, 4);
		return sessionKey;
	}
	
	/*
	 * DES encrypt
	 */
	public static byte[] encrypt(byte[] myIV, byte[] myKey, byte[] myMsg) {
		byte[] cipherText = null;

		try {
			IvParameterSpec iv = new IvParameterSpec(myIV);
			DESKeySpec desKey = new DESKeySpec(myKey);
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
			SecretKey key = keyFactory.generateSecret(desKey);

			Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, key, iv);
			cipherText = cipher.doFinal(myMsg);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

		return cipherText;
	}

	/*
	 * DES decrypt
	 */
	public static byte[] decrypt(byte[] myIV, byte[] myKey, byte[] myMsg) {
		byte[] plainText = null;

		try {
			IvParameterSpec iv = new IvParameterSpec(myIV);
			DESKeySpec desKey = new DESKeySpec(myKey);
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
			SecretKey key = keyFactory.generateSecret(desKey);

			Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, key, iv);
			plainText = cipher.doFinal(myMsg);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

		return plainText;
	}

	/*
	 * calculate MAC using DES in CBC mode
	 * The first 4 bytes of the last block will be the result
	 */
	public static byte[] calculateMac(byte[] IV, byte[] key, byte[] data){
		//padded with zeros
		int lenPad = data.length % 8;
		byte[] padded = new byte[data.length + (8-lenPad)%8];
		
		System.arraycopy(data, 0, padded, 0, data.length);
		
		for (int i = 0; i < (8-lenPad)%8; i++){
			padded[data.length + i] = (byte)0x00;
		}
		
		byte[] previousBlock = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
		byte[] enc_block = new byte[8];
		
		//encipher in CBC mode
		for (int i = 0; i < padded.length/8; i++){
			byte[] currentBlock = new byte[8];
			
			//first the current block needs to be XORed with the previously encrypted block, result in xored array
			System.arraycopy(padded, i * 8, currentBlock, 0, 8);
			
			byte[] xored = new byte[8];
			
			for (int j = 0; j < 8; j++){
				xored[j] = (byte)(currentBlock[j] ^ previousBlock[j]);
			}
			
			enc_block = encrypt(IV, key, xored);
			
			//assign the new chunk to the previous chunk
			System.arraycopy(enc_block, 0, previousBlock, 0, enc_block.length);
		}
		
		byte[] result = new byte[4];
		System.arraycopy(enc_block, 0, result, 0, 4);
		
		return result;
	}
}
